要如何開發支援下圖呈現的 Lambda Expression 功能,本篇以 Kuick ORM 為例,詳細說明。
這篇分享主將細談到 Kuick OMR 對於 Lambda Expression 的實作,請先到 kuick.codeplex.com 下載原始檔。
Kuick ORM 裡有關於 Lambda Expression 的類別,包含下面 10 個類別:
01. Kuick.Data.Entity
02. Kuick.Data.Entity<T>
03. Kuick.Data.Sql
04. Kuick.Data.Sql<T>
05. Kuick.Data.SqlAggregate
06. Kuick.Data.SqlCriterion
07. Kuick.Data.SqlExpression
08. Kuick.Data.SqlOrderBy
09. Kuick.Data.SqlSet
10. Kuick.Data.DataUtility
這 10 個類別對於 Lambda Expression 功能的實作,收歛於 Kuick.Data.DataUtility 類別裡,DataUtility 包含 10 個方法,其中與 Lambda Expression 有關的方法有 8 個,依據方法的傳入參數分成 2 種:
一、傳入參數為『Expression<Func<T, object>>』的方法,其中 T 是指實作 IEntity 的物件
實作這個傳入參數,讓 Entity, Sql 相關的類別可以編寫 3 種表示式:
1. 欄位選取
public static Column ToColumn<T>(Expression<Func<T, object>> expression) where T : IEntity
2. 條件運算
public static SqlCriterion<T> ToSqlCriterion<T>(Expression<Func<T, object>> expression) where T : class, IEntity, new()
public static SqlExpression ToSqlExpression<T>(Expression<Func<T, object>> expression) where T : IEntity
3. 資料設定
public static SqlSet ToSqlSet<T>(Expression<Func<T, object>> expression) where T : IEntity
二、傳入參數為『Expression』的方法
擁有 Expression 傳入參數的方法,主要用途是提供給第一種方法的進階處理,分成 3 種作用:
1. 選取欄位
選取欄位是針對 Convert, MemberAccess, Call 的 ExpressionType 進行各別處理,請參閱原始程式。
internal static Column ToColumn<T>(Expression expression)
where T : IEntity
{
if(null == expression) { return null; }
MemberInfo memberInfo = null;
MemberExpression memberExpression = null;
if(expression.NodeType == ExpressionType.Convert) {
memberExpression = ((UnaryExpression)expression).Operand as MemberExpression;
memberInfo = memberExpression.Member;
} else if(expression.NodeType == ExpressionType.MemberAccess) {
memberExpression = expression as MemberExpression;
memberInfo = memberExpression.Member;
} else if(expression.NodeType == ExpressionType.Call) {
memberInfo = ((MethodCallExpression)expression).Method;
} else {
throw new NotImplementedException();
}
T schema = EntityCache.Find<T>();
PropertyInfo info = memberInfo as PropertyInfo;
string name = info.Name == Constants.Entity.KeyName
? Reflector.GetValue(info, schema) as string
: info.Name;
Column column = schema.GetColumn(name);
return column;
}
2. 建立查詢條件
這個方法對於 UnaryExpression, MemberExpression, BinaryExpression 進行各別處理,請參閱原始程式。
public static SqlCriterion<T> ToSqlCriterionMain<T>(Expression expression)
where T : class, IEntity, new()
{
SqlCriterion<T> c = new SqlCriterion<T>();
// UnaryExpression
UnaryExpression unaryExpression = expression as UnaryExpression;
if(null != unaryExpression) {
if(unaryExpression.NodeType == ExpressionType.Not) {
c.SetNot();
}
c.Criteria.Add(ToSqlCriterionMain<T>(unaryExpression.Operand));
//BinaryExpression binaryExp = (BinaryExpression)unaryExpression.Operand;
//Column column = ToColumn<T>(binaryExp.Left);
//SqlOperator opt = SqlExpression.FromExpressionType(binaryExp.NodeType);
//object value = GetValue(binaryExp.Right);
//c.Where(ToSqlExpression(column, opt, value));
return c;
}
// MemberExpression
MemberExpression memberExpression = expression as MemberExpression;
if(null != memberExpression) {
Column column = ToColumn<T>(expression);
SqlExpression sqlExpression = SqlExpression.Column(column);
c.Where(sqlExpression);
return c;
}
// BinaryExpression
BinaryExpression binaryExpression = expression as BinaryExpression;
if(null != binaryExpression) {
BinaryExpression innerBinaryExp = binaryExpression.Left as BinaryExpression;
if(null == innerBinaryExp) {
Column column = ToColumn<T>(binaryExpression.Left);
SqlOperator opt = SqlExpression.FromExpressionType(expression.NodeType);
object value = GetValue(binaryExpression.Right);
c.Where(ToSqlExpression(column, opt, value));
return c;
} else {
switch(expression.NodeType) {
case ExpressionType.And:
c.Logic = SqlLogic.And;
break;
case ExpressionType.Or:
c.Logic = SqlLogic.Or;
break;
default:
Logger.Error(
"Kuick.Data.DataUtility.ToSqlCriterion",
"Unhandled NodeType of BinaryExpression.",
new Any("Expression", binaryExpression.ToString()),
new Any("Node Type", expression.NodeType)
);
throw new NotImplementedException();
}
c.Where(ToSqlCriterionMain<T>(binaryExpression.Left));
c.Where(ToSqlCriterionMain<T>(binaryExpression.Right));
return c;
}
}
//
throw new NotImplementedException(string.Format(
"Not implemented the Expression type of '{0}'.",
expression.GetType().Name
));
}
3. 取值
這個方法對於 ConstantExpression, MemberExpression, NewExpression, MethodCallExpression, BinaryExpression 進行各別處理,請參閱原始程式。
public static object GetValue(MemberExpression expression)
{
var objectMember = Expression.Convert(expression, typeof(object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
public static object GetValue(Expression expression)
{
object value = null;
// ConstantExpression
ConstantExpression constExp = expression as ConstantExpression;
if(null != constExp) { return constExp.Value; }
// MemberExpression
MemberExpression memExp = expression as MemberExpression;
if(null != memExp) {
value = GetValue(memExp);
return value;
}
// NewExpression
NewExpression newExp = expression as NewExpression;
if(null != newExp) {
List<object> list = new List<object>();
foreach(Expression exp in newExp.Arguments) {
object obj = GetValue(exp);
list.Add(obj);
}
value = newExp.Constructor.Invoke(list.ToArray());
return value;
}
// MethodCallExpression
MethodCallExpression methodCallExp = expression as MethodCallExpression;
if(null != methodCallExp) {
ConstantExpression ce = methodCallExp.Arguments[0] as ConstantExpression;
if(ce != null) {
value = ce.Value.ToString();
return value;
}
MemberExpression me = methodCallExp.Arguments[0] as MemberExpression;
if(me != null) {
value = GetValue(me);
return value;
}
switch(methodCallExp.Method.Name) {
case "Contains":
value = " like '%" + value + "%'";
return value;
case "StartsWith":
value = " like '" + value + "%'";
return value;
case "EndsWith":
value = " like '%" + value + "'";
return value;
default:
Logger.Error(
"Kuick.Data.DataUtility.GetValue",
"Unhandled Method of MethodCallExpression.",
new Any("Expression", methodCallExp.ToString()),
new Any("Method Name", methodCallExp.Method.Name)
);
throw new NotImplementedException();
}
}
// BinaryExpression
BinaryExpression binaryExp = expression as BinaryExpression;
if(null != binaryExp) {
object left = GetValue(binaryExp.Left);
object right = GetValue(binaryExp.Right);
// String
if(binaryExp.Type.IsString()) {
value = string.Concat(left, right);
return value;
}
// Integer
if(binaryExp.Type.IsInteger()) {
switch(binaryExp.NodeType) {
case ExpressionType.Add:
value = (int)left + (int)right;
break;
case ExpressionType.Subtract:
value = (int)left - (int)right;
break;
case ExpressionType.Negate:
value = -(int)left;
break;
case ExpressionType.Multiply:
value = (int)left * (int)right;
break;
case ExpressionType.Increment:
value = (int)left + 1;
break;
case ExpressionType.Divide:
value = (int)left / (int)right;
break;
default:
Logger.Error(
"Kuick.Data.DataUtility.GetValue",
"Unhandled NodeType of BinaryExpression.",
new Any("Expression", binaryExp.ToString()),
new Any("Value Type", binaryExp.Type.Name),
new Any("Node Type", binaryExp.NodeType)
);
throw new NotImplementedException();
}
return value;
}
// Boolean
if(binaryExp.Type.IsBoolean()) {
switch(binaryExp.NodeType) {
case ExpressionType.And:
case ExpressionType.AndAlso:
value = (bool)left && (bool)right;
break;
case ExpressionType.Or:
case ExpressionType.OrAssign:
value = (bool)left || (bool)right;
break;
case ExpressionType.IsTrue:
value = null == left ? (bool)right : (bool)left;
break;
case ExpressionType.IsFalse:
value = null == left ? !(bool)right : !(bool)left;
break;
default:
Logger.Error(
"Kuick.Data.DataUtility.GetValue",
"Unhandled NodeType of BinaryExpression.",
new Any("Expression", binaryExp.ToString()),
new Any("Value Type", binaryExp.Type.Name),
new Any("Node Type", binaryExp.NodeType)
);
throw new NotImplementedException();
}
return value;
}
}
Logger.Error(
"Kuick.Data.DataUtility.GetValue",
"Unhandled Expression Type.",
new Any("Type Name", expression.GetType().Name)
);
throw new NotImplementedException();
}
近日 Kuick 將有新的一版更新,如果您發現有錯誤,歡迎您直接與我聯絡 kevinjong@gmail.com 謝謝。
====================================================
這篇分享提及 Sql 操作相關的類別,下篇分享 (Sql Command 物件化) 來說說 Sql Command 與這些類別之間的關係:
01. Kuick.Data.Sql
02. Kuick.Data.Sql<T>
03. Kuick.Data.SqlAggregate
04. Kuick.Data.SqlExpression
05. Kuick.Data.SqlCriterion
06. Kuick.Data.SqlCriterion<T>
07. Kuick.Data.SqlJoin
08. Kuick.Data.SqlLiteral
09. Kuick.Data.SqlOrderBy
10. Kuick.Data.SqlSet
========================================
鐵人賽分享列表:Kuick Application & ORM Framework
開放原始碼專案:kuick.codeplex.com
直接下載原始碼:Kuick
下載相關文件檔:C# Code Conventions and Design Guideline
相關教學影片區:Kuick on YouTube